var express = require('express');
var router = express.Router();
var config = require('../config');
var multiparty = require('multiparty');
var util = require('util');
var db = require('../db').get();
var directoryCl = db['directory'],
    tagCl = db['tag'],
    imageCl = db['image'],
    configCl = db['config'];
var path = require('path'),
    fs = require('fs'),
    utils = require('../utils');



router.get('/*', function(req, res, next) {
    req._id = req.query._id || '';
    req.args = JSON.parse(req.query.args || '{}');
    next();
});

router.post('/*', function(req, res, next) {
    req._id = req.body._id || '';
    req.args = JSON.parse(req.body.args || '{}');
    next();
});

function createError(msg) {
    return {
        status: 400,
        ajaxMsg: true,
        message: msg
    }
}

function has(model, _ids) {
    _ids = [].concat(_ids);
    if (!_ids.length) {
        return Promise.reject(createError('require not empty array'));
    }
    for (var _id of _ids) {
        if (!_id) {
            return Promise.reject(createError('require not empty _id'));
        }
    }
    return model.countDocuments().where('_id').in(_ids).then(c => {
        return c == _ids.length ? Promise.resolve() : Promise.reject(createError('some id not found'));
    });
}

function getRootID() {
    return _getDirectoryID('root_dir', 'Root');
}

function getRecycledDirectoryID() {
    return _getDirectoryID('recycle_directory', 'Recycle');
}

function getRecycledImageDirectoryID() {
    return _getDirectoryID('recycle_image', 'Recycle for Images');
}

function destroyFile(src) {
    var file_name = path.basename(src), extname = path.extname(file_name);
    try {
        fs.unlink(utils.getOriginalName(file_name), () => {});
        fs.unlink(utils.getThumbName(file_name), () => {});
    } catch(e) {

    }
}

function deleteSomeIfIdle() {
    getToDeleteDirectoryID()
    .then(dr_id => {
        return directoryCl.read(dr_id)
    })
    .then(delete_doc => {
        delete_doc.next;
        delete_doc.content;
        var toDeleteImages = delete_doc.content.slice(0, 10);
        var toDeleteDirectorys = delete_doc.next.slice(0, 10);
        if (!toDeleteDirectorys.length && !toDeleteImages.length) {
            deleteSomeIfIdle.delay = 3000;
        } else {
            console.log('a round for delete');
            console.log('\tdirectory:', toDeleteDirectorys);
            console.log('\timage:', toDeleteImages);
        }
        var next_arr = [], next_content = [];
        // 没有环检测
        return directoryCl.removeImages(delete_doc._id, toDeleteImages)
        .then(() => directoryCl.removeNext(delete_doc._id, toDeleteDirectorys))
        .then(
            () => {
                return Promise.all(
                    toDeleteDirectorys.map(x => directoryCl.delete(x))
                ).then(function(docs) {
                    docs.forEach(doc => {
                        next_arr.push.apply(next_arr, doc.next);
                        next_content.push.apply(next_content, doc.content);
                    });
                    
                })
            }
        )
        .then(() => Promise.all( toDeleteImages.map( x => imageCl.delete(x, destroyFile) ) ) )
        .then(() => directoryCl.addImages(delete_doc._id, next_content))
        .then(() => directoryCl.addNext(delete_doc._id, next_arr))
    })
    .then(() => {
        setTimeout(deleteSomeIfIdle, deleteSomeIfIdle.delay);
    })
    .catch((e) => {
        console.warn('error occur in delete: ', e);
        deleteSomeIfIdle.delay = 3000;
        setTimeout(deleteSomeIfIdle, deleteSomeIfIdle.delay);
    })
}

deleteSomeIfIdle.delay = 1000;
deleteSomeIfIdle();

function getToDeleteDirectoryID() {
    return _getDirectoryID('delete', 'directorys and images to delete');
}

function _getDirectoryID(key, name) {
    // Create directory if not existed
    return configCl.getValue(key)
    .then(null, function() {
      return directoryCl.create(name)
        .then((doc) => {
            return configCl.setConfig(key, doc._id);
        })
        .then((doc) => {
            return Promise.resolve(doc.value);
        })
    })
}

router.post('/directory/read', function(req, res, next) {
    var root_dir_id = null;
    getRootID()
    .then(function(_id) {
        root_dir_id = _id;
        return directoryCl.listAll();
    })
    .then(docs => {
        res.json({
            root: root_dir_id,
            dirs: docs.map(x => x.toJSON())
        })
    })
});

router.post('/directory/list', function(req, res, next) {
    directoryCl.listNext(req._id).then(([doc, docs]) => {
        res.json({
            self: doc.toJSON(),
            dirs: docs.map(x => x.toJSON())
        })
    })
})

router.post('/directory/list/root', function(req, res, next) {
    getRootID()
    .then(function(_id) {
        return directoryCl.listNext(_id);
    })
    .then(([doc, docs]) => {
        res.json({
            rootId: doc._id,
            root: doc.toJSON(),
            dirs: docs.map(x => x.toJSON())
        })
    })
})

router.post('/directory/list/recycle/directory', function(req, res, next) {
    getRecycledDirectoryID()
    .then(function(_id) {
        return directoryCl.listNext(_id);
    })
    .then(([doc, docs]) => {
        res.json({
            rootId: doc._id,
            root: doc.toJSON(),
            dirs: docs.map(x => x.toJSON())
        })
    })
})

router.post('/directory/list/recycle/image', function(req, res, next) {
    getRecycledImageDirectoryID()
    .then(function(_id) {
        return directoryCl.listNext(_id);
    })
    .then(([doc, docs]) => {
        res.json({
            rootId: doc._id,
            root: doc.toJSON(),
            dirs: docs.map(x => x.toJSON())
        })
    })
})

router.post('/directory/create', function(req, res, next) {
    var parent_id = req.args.parent, name = req.args.name;
    var parentDoc = null, selfDoc = null;
    has(directoryCl, [parent_id])
    .then(child_name_check.bind(null, parent_id, name))
    .then(function() {
        return directoryCl.create(name)
    })
    .then(function(doc) {
        selfDoc = doc;
        return directoryCl.addNext(parent_id, [doc._id])
        .then((parent) => {
            parentDoc = parent;
        })
    })
    .then(function() {
        res.json({
            parent: parentDoc.toJSON(),
            self: selfDoc.toJSON()
        })
    })
    .catch(err => {
        next(err);
    })
});

router.post('/directory/addNext', function(req, res, next) {
    console.warn('directory:addNext is unsafe, did not check circle', req._id, req.args.child_id);
    console.warn('deprecated, using directory:copy instead');
    var child_id = [].concat(req.args.child_id);
    has(directoryCl, child_id.concat(req._id))
    .then(function() {
        return directoryCl.addNext(req._id, child_id);
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
});

router.post('/directory/copy', function(req, res, next) {
    // ircorrect
    var parent_id = req._id, child_id = [].concat(req.args.child_id), dest_id = req.args.dest_id;
    has(directoryCl, child_id.concat(parent_id, dest_id))
    .then(function() {
        return Promise.all(
            child_id.map(_id => directoryCl.isValidToAdd(dest_id, _id))
        )
    })
    .then(function() {
        return Promise.all(
            child_id.map(_id => directoryCl.move(parent_id, _id, dest_id))
        )
    })
    .then(([parent_doc, docs]) => {
        res.json({
            parent: parent_doc.toJSON(),
            docs: docs.toJSON()
        })
    })
})

router.post('/directory/move', function(req, res, next) {
    var parent_id = req._id, child_id = [].concat(req.args.child_id), dest_id = req.args.dest_id;
    has(directoryCl, child_id.concat(parent_id, dest_id))
    .then(function() {
        return Promise.all(
            child_id.map(_id => directoryCl.isValidToAdd(dest_id, _id))
        )
    })
    .then(function() {
        return directoryCl.move(parent_id, child_id, dest_id);
    })
    .then(([parent_doc, dest_doc]) => {
        res.json({
            parent: parent_doc.toJSON(),
            dest: dest_doc.toJSON()
        })
    })
    .catch(() => {
        next(createError('目录名重复'))
    })
})

router.post('/directory/removeNext', function(req, res, next) {
    var child_id = [].concat(req.args.child_id);
    has(directoryCl, child_id.concat(req._id))
    .then(getRecycledDirectoryID)
    .then(function(_id) {
        if (req._id != _id) {
            // 不用检测名字冲突
            return directoryCl.move(req._id, child_id, _id);
        } else {
            var removedArr = [], doc;
            return getToDeleteDirectoryID()
            .then(function(dr_id) {
                return directoryCl.move(_id, child_id, dr_id);
            })
            .then(function([recycle_doc, delete_doc]) {
                return Promise.resolve([recycle_doc, recycle_doc]);
            })
        }
    })
    .then(function([parent_doc, recycle_doc]) {
        res.json({
            self: parent_doc.toJSON()
        })
    })
});

router.post('/directory/copyImages', async function(req, res, next) {
    var child_id = [].concat(req.args.child_id), dest_id = req.args.dest_id;
    var child_entities = [];
    try {
        await has(directoryCl, [dest_id]);
        await has(imageCl, child_id);
        for (var i = 0; i < child_id.length; i++) {
            child_entities.push(await imageCl.clone(child_id[i]));
        }
        var doc = await directoryCl.addImages(dest_id, child_entities.map(x => x._id));
        res.json({
            self: doc.toJSON()
        })
    } catch(e) {
        next(createError('some error occur when copy images'));
    }
});

router.post('/directory/moveImages', async function(req, res, next) {
    var child_id = [].concat(req.args.child_id), dest_id = req.args.dest_id;
    var parent_id = req._id;
    var removedArr = [];
    if (parent_id == dest_id) {
        return next(createError('移动操作无效：同一个目录。'));
    }
    try {
        await has(directoryCl, [dest_id, parent_id]);
        await has(imageCl, child_id);
        var self = (await directoryCl.removeImages(parent_id, child_id, removedArr)).toJSON();
        var dest = (await directoryCl.addImages(dest_id, removedArr)).toJSON();
        res.json({
            self, dest
        })
    } catch(e) {
        next(createError('some error occur when copy images'));
    }
});

router.post('/directory/addImages', function(req, res, next) {
    console.warn('deprecated, using directory:copyImages instead');
    var child_id = [].concat(req.args.child_id);
    has(directoryCl, [req._id])
    .then(function() {
        return has(imageCl, child_id);
    })
    .then(function() {
        return directoryCl.addImages(req._id, child_id);
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
});

router.post('/directory/removeImages', function(req, res, next) {
    var child_id = [].concat(req.args.child_id);
    var removedArr = [];
    has(directoryCl, [req._id])
    .then(function() {
        return has(imageCl, child_id);
    })
    .then(getRecycledImageDirectoryID)
    .then(function(recycle_image_id) {
        return directoryCl.removeImages(req._id, child_id, removedArr)
        .then(doc => {
            var promise;
            if (recycle_image_id != req._id) {
                promise = directoryCl.addImages(recycle_image_id, removedArr)
            } else {
                promise = getToDeleteDirectoryID().then(dr_id => {
                    return directoryCl.addImages(dr_id, removedArr);
                });
            }
            return promise.then(() => {return Promise.resolve(doc);});
        });
    })
    .then(function(doc) {

        res.json({
            self: doc.toJSON()
        })
    })
});

router.post('/tag/read', function(req, res, next) {
    tagCl.listAll()
    .then(function(docs) {
        res.json({
            tags: docs.map(x => x.toJSON())
        })
    })
});

router.post('/tag/create', function(req, res, next) {
    var name = req.args.name;
    tag_name_check(name)
    .then(function() {
        return tagCl.create(name)
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
    .catch(err => {
        next(err);
    })
});

router.post('/tag/delete', function(req, res, next) {
    has(tagCl, req._id)
    .then(function() {
        return tagCl.delete(req._id)
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
});

router.post('/tag/addImages', function(req, res, next) {
    var child_id = [].concat(req.args.child_id);
    has(tagCl, [req._id])
    .then(function() {
        return has(imageCl, child_id);
    })
    .then(function() {
        return tagCl.addImages(req._id, child_id);
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
});

router.post('/tag/removeImages', function(req, res, next) {
    var child_id = [].concat(req.args.child_id);
    has(tagCl, [req._id])
    .then(function() {
        return has(tagCl, child_id);
    })
    .then(function() {
        return tagCl.removeImages(req._id, child_id);
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
});

router.post('/image/create', function(req, res, next) {
    var form = new multiparty.Form({
        uploadDir: config.tempFileDirectory
    }), targetDir = config.imageDirectory,
    thumbDir = config.thumbDirectory;
    
    form.parse(req, function(err, fields, files) {
        if (err) {
            return next(err);
        }
        var directory = fields.directory;
        has(directoryCl, [directory])
        .then(function() {
            return Promise.all(
                files.file.filter(image => {
                    var extname = path.extname(image.path);
                    return /^\.(jpg|jpeg|png|webp|gif|bmp|svg)$/i.test(extname);
                }).map(function(image) {
                    return createImageEntity(image);
                })
            )
        })
        .then(arr => {
            var _ids = arr.map(x => x._id);
            directoryCl.addImages(directory, _ids)
            .then(function(doc) {
                res.json({
                    directory: doc.toJSON(),
                    images: arr.map(x => x.toJSON())
                })
            })
        })
      
    });
    function createImageEntity(img) {
        return imageCl.createFromFile(img, {
            name: img.originalFilename,
            targetDir,
            thumbDir
        }).then(function(doc) {
            fs.unlinkSync(img.path);
            return Promise.resolve(doc);
        });
        
    }
});

router.post('/image/read', function(req, res, next) {
    imageCl.listAll()
    .then(docs => {
        res.json({
            images: docs.map(x => x.toJSON())
        })
    })
});

router.post('/image/list', async function(req, res, next) {
    var _id = req._id;
    try {
        await has(directoryCl, _id);
        var doc = await directoryCl.read(_id);
        var docs = await imageCl.listSome(doc.content);
        res.json({
            images: docs.map(x => x.toJSON())
        });
    } catch(e) {
        next(createError('wrong directory id'));
    }
})

router.post('/image/addExtendInfo', function(req, res, next) {
    has(imageCl, [req._id])

    .then(function() {
        var info = req.args.info || {};
        info = typeof info === "string" ?
            {
                title: '无',
                content: info
            }
        :
            {
                title: info.title || '',
                content: info.content || ''
            };
        if (!info.content) {
            return Promise.reject(createError('内容不能为空'));
        }
        return imageCl.addExtendInfo(req._id, info);
    })
    .then(doc => {
        res.json({
            self: doc.toJSON()
        })
    })
});

function nameHandler(model, validCheck) {
    return function(req, res, next) {
        var name = req.args.name;
        if (!name) {
            return next(createError('名字为空'));
        }
        has(model, [req._id])
        .then(function() {
            return validCheck && validCheck(req, res, next);
        })
        .then(function() {
            return model.rename(req._id, name);
        })
        .then(function(doc) {
            res.json({
                self: doc.toJSON()
            })
        })
        .catch(err => {
            next(err);
        })
    }
}

function child_name_check(_id, name) {
    return directoryCl.read(_id).then(function(doc) {
        return directoryCl.countDocuments()
        .where('_id').in(doc.next)
        .where('name').equals(name)
        .then(x => {
            if (x != 0) {
                return Promise.reject(createError('目录名重复'));
            }
        })
    })
}

function tag_name_check(name) {
    return tagCl.countDocuments().where('name').equals(name).then(x => {
        if (x) {
            return Promise.reject(createError('tag name conflic'));
        }
    })
}

router.post('/directory/rename', nameHandler(directoryCl, function(req, res, next) {
    var _id = req._id;
    return directoryCl.where('next').in([_id]).then(docs => {
        return Promise.all(
            docs.map(x => child_name_check(x._id, req.args.name))
        );
    })
}));

router.post('/tag/rename', nameHandler(tagCl, function(req, res, next) {
    return tag_name_check(req.args.name);
}));

router.post('/image/rename', nameHandler(imageCl));

router.post('/image/updateExtendInfo', function(req, res, next) {
    var info = req.args.info;
    if (!info.content) {
        return next(createError('content empty'));
    }
    has(imageCl, [req._id])
    .then(function() {
        return imageCl.updateExtendInfo(req._id, info);
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
});

router.post('/image/removeExtendInfo', function(req, res, next) {
    var info = req.args.info;
    has(imageCl, [req._id])
    .then(function() {
        return imageCl.removeExtendInfo(req._id, info);
    })
    .then(function(doc) {
        res.json({
            self: doc.toJSON()
        })
    })
})

router.post('/image/group/similary', async function(req, res, next) {
    var d_id = req._id,
        di = ('di' in req.args) ? req.args.di : 8;
    try {
        await has(directoryCl, d_id);
        var doc = await directoryCl.read(d_id);
        var rs = await imageCl.groupBySimilary(doc.content, di);
        res.json({
            result: rs
        });
    } catch(e) {
        next(createError('some error occur when groupe images by similary'));
    }
    
})

router.post('/directory/searchImageByImage', function(req, res, next) {
    var form = new multiparty.Form({
        uploadDir: config.tempFileDirectory
    });
    
    form.parse(req, async function(err, fields, files) {
        if (err) {
            return next(err);
        }
        var _id = fields._id, di = ('di' in fields) ? fields[di] : 8;
        try {
            await has(directoryCl, [_id]);
            var img = files.file.filter(image => {
                var extname = path.extname(image.path);
                return /^\.(jpg|jpeg|png|webp|gif|bmp|svg)$/i.test(extname);
            })[0];

            if (!img) {
                throw "unknown extname";
            }

            var directory = await directoryCl.read(_id);
            var a_hash = await utils.get_aHash(img.path),
                b_hash = await utils.get_bHash(img.path),
                a_hash_d = utils.K.HASH8.arr.map(x => utils.distanceOf(x, a_hash));
            console.log(`search image with: aHash_8 ${a_hash} bHash_8 ${b_hash}`);
            var hashQ = utils.toQueryObj('baseInfo.aHash_8_d', a_hash_d, di);
            hashQ._id = { $in: directory.content };

            var rs = await imageCl.find(hashQ).select('baseInfo.aHash_8 baseInfo.bHash_8');

            res.json({
                result: rs
                    .filter((x, i) => {
                        return utils.isSimilary([a_hash, b_hash], [x.baseInfo.aHash_8, x.baseInfo.bHash_8], di);
                    }).map(x => x._id.toString())
            });

        } catch(e) {
            next(createError('failed'));
        } finally {
            files.file.forEach(x => {
                fs.unlinkSync(x.path);
            });
        }
      
    });
});

module.exports = router;
